using UnityEngine;
using PathX.Collection;
using FPLibrary;

namespace PathX
{
    public interface IPathNode : IGridCell,IPositioned
    {
        int g { get; set; }
        
        int h { get; set; }
        
        int f { get; set; }
        
        bool isClosed { get; set; }
        
        IPathNode predecessor { get; set; }

        void GetWalkableNeighbours(DynamicArray<IPathNode> neighbours, IUnitProperties unitProps,
            bool cornerCuttingAllowed, bool preventDiagonalMoves);

        bool TryGetWalkableNeighbour(int dx, int dz, IUnitProperties unitProps, DynamicArray<IPathNode> neighbours);
    }

    public struct VectorXZ
    {
        public int x;
        public int z;
    }

    /// <summary>
    /// Interface for grid cell basic properties.
    /// </summary>
    public interface IGridCell : IPositioned
    {
        /// <summary>
        /// Gets the parent cell matrix.
        /// </summary>
        /// <value>
        /// The parent matrix.
        /// </value>
        CellMatrix parent { get; }

        /// <summary>
        /// Gets the cell's x position in the grid matrix
        /// </summary>
        /// <value>
        /// The matrix position x.
        /// </value>
        int matrixPosX { get; }

        /// <summary>
        /// Gets the cell's z position in the grid matrix
        /// </summary>
        /// <value>
        /// The matrix position z.
        /// </value>
        int matrixPosZ { get; }

        /// <summary>
        /// Gets the arbitrary cost of walking this cell.
        /// </summary>
        /// <value>
        /// The cost.
        /// </value>
        int cost { get; }

        /// <summary>
        /// Gets or sets a value indicating whether this cell is permanently blocked.
        /// Note that this is automatically set depending on level geometry when the Grid initializes, but it can also be changed manually.
        /// </summary>
        bool isPermanentlyBlocked { get; set; }

        /// <summary>
        /// Determines whether the cell is walkable. This check does not take unit size into consideration.
        /// </summary>
        /// <param name="mask">The unit's attributes.</param>
        /// <returns>
        ///   <c>true</c> if the cell is walkable, otherwise <c>false</c>
        /// </returns>
        bool IsWalkable(AttributeMask mask);

        /// <summary>
        /// Determines whether the cell is walkable to a certain unit / unit type.
        /// </summary>
        /// <param name="unitProps">The unit properties.</param>
        /// <returns><c>true</c> if the cell is walkable, otherwise <c>false</c></returns>
        bool IsWalkableWithClearance(IUnitProperties unitProps);

        /// <summary>
        /// Determines whether the cell is walkable from the specified neighbour. This check does not take unit size into consideration.
        /// </summary>
        /// <param name="neighbour">The neighbour.</param>
        /// <param name="unitProps">The unit properties.</param>
        /// <returns><c>true</c> if the cell is walkable, otherwise <c>false</c></returns>
        bool IsWalkableFrom(IGridCell neighbour, IUnitProperties unitProps);

        /// <summary>
        /// Determines whether the cell is walkable from the specified neighbour.
        /// </summary>
        /// <param name="neighbour">The neighbour.</param>
        /// <param name="unitProps">The unit properties.</param>
        /// <returns><c>true</c> if the cell is walkable, otherwise <c>false</c></returns>
        bool IsWalkableFromWithClearance(IGridCell neighbour, IUnitProperties unitProps);

        /// <summary>
        /// Gets the neighbour at the specified matrix offset.
        /// </summary>
        /// <param name="offset">The offset.</param>
        /// <returns>The neighbour cell or null</returns>
        Cell GetNeighbour(VectorXZ offset);

        /// <summary>
        /// Gets the neighbour (or other cell for that matter) at the specified matrix index.
        /// </summary>
        /// <param name="dx">The x offset.</param>
        /// <param name="dz">The z offset.</param>
        /// <returns>he neighbour cell or null</returns>
        Cell GetNeighbour(int dx, int dz);

        /// <summary>
        /// Gets all the neighbours of a cell.
        /// </summary>
        /// <param name="neighboursBuffer">The buffer to fill with neighbours, this must have a size of 8 (or more).</param>
        /// <returns>The number of neighbours (ranging from 3 to 8)</returns>
        int GetNeighbours(Cell[] neighboursBuffer);
    }

    public interface IPathRequest
    {
        /// <summary>
        /// Gets or sets where to move from.
        /// </summary>
        FPVector from { get; set; }

        /// <summary>
        /// Gets or sets where to move to.
        /// </summary>
        FPVector to { get; set; }

        /// <summary>
        /// Gets or sets the points in between <see cref="from"/> and <see cref="to"/> that the path should include.
        /// </summary>
        FPVector[] via { get; set; }

        /// <summary>
        /// Gets or sets the requester's properties.
        /// </summary>
        /// <value>
        /// The requester properties.
        /// </value>
        IUnitProperties requesterProperties { get; set; }
        
        /// <summary>
        /// Gets or sets the options used during the path finding process.
        /// </summary>
        /// <value>
        /// The path finder options.
        /// </value>
        IPathFinderOptions pathFinderOptions { get; set; }

        /// <summary>
        /// Gets a value indicating whether this instance is valid.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is valid; otherwise, <c>false</c>.
        /// </value>
        bool isValid { get; }

        /// <summary>
        /// Gets or sets a value indicating whether this request has decayed. A decayed request will not be processed by the path finder, unless it already is being processed.
        /// </summary>
        /// <value>
        /// <c>true</c> if this request has decayed; otherwise, <c>false</c>.
        /// </value>
        bool hasDecayed { get; set; }

        /// <summary>
        /// Gets or sets custom data, which can be used by e.g. pre and post processors to communicate.
        /// </summary>
        object customData { get; set; }

        /// <summary>
        /// Completes this request
        /// </summary>
        /// <param name="result">The result.</param>
        void Complete(PathResult result);
    }

    public class PathResult
    {
        private static readonly Path _pathEmpty = new Path();
        private PathingStatus _status;

        /// <summary>
        /// Initializes a new instance of the <see cref="PathResult"/> class.
        /// </summary>
        /// <param name="status">The status.</param>
        /// <param name="path">The path.</param>
        /// <param name="pathCost">The cost of the path, i.e. its length and combined cost of the cells involved</param>
        /// <param name="originalRequest">The original request.</param>
        public PathResult(PathingStatus status, Path path, int pathCost, IPathRequest originalRequest)
        {
            this.status = status;
            this.path = path ?? _pathEmpty;
            this.pathCost = pathCost;
            this.originalRequest = originalRequest;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PathResult"/> class.
        /// </summary>
        protected PathResult()
        {
        }

        /// <summary>
        /// Gets or sets the status.
        /// </summary>
        /// <value>
        /// The status.
        /// </value>
        public PathingStatus status
        {
            get { return _status; }
            set { _status = value; }
        }

        /// <summary>
        /// Gets or sets the error information. Consult this if the path request fails for unknown reasons.
        /// </summary>
        /// <value>
        /// The error information.
        /// </value>
        public string errorInfo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the path.
        /// </summary>
        /// <value>
        /// The path.
        /// </value>
        public Path path
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the path cost. The cost is a number that represents the length of the path combined with the cost of the nodes along it.
        /// </summary>
        /// <value>
        /// The path cost.
        /// </value>
        public int pathCost
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the original request.
        /// </summary>
        /// <value>
        /// The original request.
        /// </value>
        public IPathRequest originalRequest
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets the inner result. This provides additional info on the result of the request in case of a partial success, e.g. status = <see cref="PathingStatus.CompletePartial"/>.
        /// In other cases this will be null.
        /// </summary>
        public InnerResultData innerResult
        {
            get;
            private set;
        }

        internal void RegisterPartialResult(PathingStatus status, FPVector[] pendingWaypoints)
        {
            this.status = PathingStatus.CompletePartial;

            this.innerResult = new InnerResultData
            {
                status = status,
                pendingWaypoints = pendingWaypoints
            };
        }

        /// <summary>
        /// Inner data class, nothing to see here.
        /// </summary>
        public class InnerResultData
        {
            /// <summary>
            /// The status of the segment that could not be completed.
            /// </summary>
            public PathingStatus status;

            /// <summary>
            /// The pending waypoints, e.g. the way points (or via points) that are not part of the returned path.
            /// </summary>
            public FPVector[] pendingWaypoints;

            /// <summary>
            /// Gets a value indicating whether this instance has pending waypoints.
            /// </summary>
            /// <value>
            /// <c>true</c> if this instance has pending waypoints; otherwise, <c>false</c>.
            /// </value>
            public bool hasPendingWaypoints
            {
                get { return this.pendingWaypoints != null && this.pendingWaypoints.Length > 0; }
            }
        }
    }
}
